/*
 * Copyright (c) 2017, MegaEase
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package canary

import (
	"errors"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"unicode"

	jwtgo "github.com/dgrijalva/jwt-go"

	"github.com/megaease/easegress/pkg/logger"
	"github.com/megaease/easegress/pkg/util/hashtool"
)

// data source.
const (
	header   = "Header"
	cookie   = "Cookie"
	jwt      = "Jwt"
	clientip = "ClientIP"
)

var sources = []string{
	header,
	cookie,
	jwt,
	clientip,
}

// sourceData is matcher's data sources.
// matcher will find data from it and try to match condition.
type sourceData struct {
	req      *http.Request
	clientIP string
}

type matcher func(data *sourceData) bool

// conditional operator
const (
	eq  = "=="
	ne  = "!="
	gt  = ">"
	lt  = "<"
	ge  = ">="
	le  = "<="
	mod = "mod"
	in  = "in"
)

// conditional operators.
var condOPs = []string{
	eq, ne, gt, lt, ge, le, mod, in,
}

// logical operator
const (
	lp  = "("
	rp  = ")"
	and = "&&"
	or  = "||"
)

// logical operators.
var logicOPs = []string{
	lp, rp, and, or,
}

var reservedWords = []string{
	header, cookie, jwt, clientip,
	ge, le, eq, ne, gt, lt, mod, in,
	lp, rp, and, or,
}

// parser parses conditions and generates matcher.
type parser struct {
	conditions string // Raw conditions.

	// The consumer offset of conditions string
	// which presents parse processing.
	offset int
	step   step
}

// step indicates parse step.
// Help to chose parse functions.
type step int

const (
	stepBegin   step = iota // stepBegin means we are in a beginning parse step.
	stepCond                // stepCond means we are in a condition parse step.
	stepLogicOP             // stepLogicOP means we are in a logic operator parse step.
	// stepCombMatcher means we are in a combine matchers parse step.
	// We need combine oldMatcher & newMatcher together.
	stepCombMatcher
)

func makeMatcher(conditions string) (matcher, error) {

	c := strings.TrimSpace(conditions)
	if len(c) == 0 {
		return nil, errors.New("empty conditions")
	}
	p := &parser{
		conditions: c,
		offset:     0,
		step:       stepBegin,
	}
	return p.doParse()
}

// doParse parses conditions and return matcher.
//
// doParse traverses conditions and tries to parse condition one by one.
// At the beginning, doParse will peek a token to see it's a condition or logical operator,
// then modifies the parse step, in the next round, doParse will follow the step flag.
// In the end, doParse will combine matchers which generated by conditions.
func (p *parser) doParse() (retMatcher matcher, err error) {

	err = p.check()
	if err != nil {
		return nil, err
	}

	retMatcher = nil // The final matcher, set it nil at start.
	var newMatcher matcher
	logicOP := ""

	// mops is a stack which is used for storing
	// matchers and operators temporally when
	// meet "(" in the parse process, and pop
	// it when meet ")".
	// Using it for dealing with priority issue.
	mops := make([]matcherOP, 0)

	for {
		if p.offset >= len(p.conditions) && p.step != stepCombMatcher { // We need combine matchers and parse finished.
			return retMatcher, nil
		}

		// new a temporal matcher here and copy retMatcher.
		// It can avoid stack exceed limit.
		var oldMatcher matcher
		oldMatcher = retMatcher

		switch p.step {

		case stepBegin: // Condition or Logic Operator.
			err = p.doParseBegin()
			if err != nil {
				return nil, err
			}

		case stepCond:
			m, err := p.doParseCond()
			if err != nil {
				return nil, err
			}
			newMatcher = m
			p.step = stepCombMatcher

		case stepLogicOP:
			op := p.pop()
			switch op {
			case and:
				logicOP = and
				p.step = stepBegin
			case or:
				logicOP = or
				p.step = stepBegin
			case lp: // Meet "(", push oldMatcher to stack.
				if oldMatcher != nil {
					mops = append(mops, matcherOP{
						m:  oldMatcher,
						op: logicOP,
					})
				}
				retMatcher = nil // Set reMatcher nil, because we already store it.
				logicOP = ""
				p.step = stepBegin
			case rp: // Meet ")", pop matcher from stack.
				mop := mops[len(mops)-1]
				logicOP = mop.op
				retMatcher = mop.m
				p.step = stepCombMatcher
			}

		case stepCombMatcher:
			// updateMatcher = newMatcher + oldMatcher
			var updateMatcher matcher
			if oldMatcher == nil {
				updateMatcher = newMatcher
			} else {
				switch logicOP {
				case and:
					updateMatcher = func(data *sourceData) bool {
						return oldMatcher(data) && newMatcher(data)
					}
				case or:
					updateMatcher = func(data *sourceData) bool {
						return oldMatcher(data) || newMatcher(data)
					}
				}
			}
			retMatcher = updateMatcher
			p.step = stepBegin
		}
	}
}

// check checks conditions.
func (p *parser) check() (err error) {
	firstToken := p.peek()
	// conditions must start with source.
	if !(isIn(firstToken, sources)) {
		return errors.New("illegal conditions")
	}

	if !isValidParenthesis(p.conditions) {
		return errors.New("illegal parenthesis")
	}

	return nil
}

// isValidParenthesis checks the string has correct "()" or not.
func isValidParenthesis(s string) bool {
	if s == "" {
		return true
	}

	cnt := 0

	for i := 0; i < len(s); i++ {
		switch s[i] {
		case '(':
			cnt++
		case ')':
			if cnt == 0 {
				return false
			}

			cnt--
		default:

		}
	}

	return cnt == 0
}

// matcherOP is a container which is used for
// storing matcher and operator temporally
type matcherOP struct {
	m  matcher
	op string
}

// try to find next parse step.
func (p *parser) doParseBegin() error {
	v := p.peek()
	if isIn(v, sources) {
		p.step = stepCond
		return nil
	}

	if isIn(v, logicOPs) {
		p.step = stepLogicOP
		return nil
	}

	return errors.New("illegal condition")
}

// getActVal gets actual value from sourceData.
type getActVal func(data *sourceData) string

// If it's condition, it must be this form (a single condition):
// <source> <conditional_op> <expect_value>
// source could be: a, a.b or a.b.c
// value could be 'x' or 'x,y,z' (if op is in)
// ps: if op is mod, and p is an integer
//
// In this function, we try to parse condition to the form
// mentioned above.
func (p *parser) doParseCond() (m matcher, err error) {
	src := p.pop()
	key := ""
	op := ""
	exp := ""

	if src != clientip {
		key, err = p.getSourceKey()
		if err != nil {
			return nil, err
		}
	}

	op, exp, err = p.getOPAndExp()
	if err != nil {
		return nil, err
	}

	// Define a getActVal function here to hide source detail.
	var getAct getActVal

	switch src { // Choose right way to handle source.
	case clientip:
		getAct = func(data *sourceData) string {
			return data.clientIP
		}
	case jwt:
		getAct = func(data *sourceData) string {
			auth := data.req.Header.Get("Authorization")
			if auth == "" {
				return ""
			}
			token := strings.Split(auth, " ")[1]
			t, _, err := new(jwtgo.Parser).ParseUnverified(token, jwtgo.MapClaims{})
			if err != nil {
				return ""
			}
			claim, ok := t.Claims.(jwtgo.MapClaims)
			if !ok {
				logger.Debugf("jwt claims is not MapClaims")
				return ""
			}
			return claim[key].(string)
		}
	case header:
		getAct = func(data *sourceData) string {
			return data.req.Header.Get(key)
		}
	case cookie:
		getAct = func(data *sourceData) string {
			c, err := data.req.Cookie(key)
			if err != nil {
				logger.Debugf("try to get cookie failed: %s", err.Error())
				return ""
			}
			return c.Value
		}
	}

	// Preprocessing special operator for accelerating matcher.
	var expMod uint32
	if op == mod {
		v, err := strconv.ParseInt(exp, 10, 64)
		if err != nil {
			return nil, err
		}
		if v < 0 {
			v = 0
			logger.Warnf("mod value < 0, set it to 0")
		}
		modv := v % 100
		logger.Debugf("%d mod 100, got: %d", v, modv)
		expMod = uint32(modv)
	}
	var expIn []string
	if op == in {
		ss := strings.Split(exp, ",")
		expIn = make([]string, len(ss))
		for i, s := range ss {
			expIn[i] = strings.TrimSpace(s)
		}
	}

	// m is made from a single condition.
	switch op {
	case eq:
		m = func(data *sourceData) bool {
			return getAct(data) == exp
		}
	case ne:
		m = func(data *sourceData) bool {
			return getAct(data) != exp
		}
	case gt:
		m = func(data *sourceData) bool {
			return getAct(data) > exp
		}
	case lt:
		m = func(data *sourceData) bool {
			return getAct(data) < exp
		}
	case ge:
		m = func(data *sourceData) bool {
			return getAct(data) >= exp
		}
	case le:
		m = func(data *sourceData) bool {
			return getAct(data) <= exp
		}
	case mod:
		m = func(data *sourceData) bool {
			return hashtool.Hash32(getAct(data))%100 < expMod
		}
	case in:
		m = func(data *sourceData) bool {
			return isIn(getAct(data), expIn)
		}
	}
	return m, nil
}

// Legal form: source.key
// pop key here.
func (p *parser) getSourceKey() (string, error) {
	if len(p.conditions) <= p.offset {
		return "", errors.New("illegal key")
	}
	if p.conditions[p.offset] != '.' {
		return "", errors.New("illegal key")
	}
	p.offset += 1
	return p.pop(), nil
}

// Legal form: <conditional operator> <expect value>
// pop conditional operator and expect value here.
func (p *parser) getOPAndExp() (string, string, error) {
	op := p.pop()
	if !isIn(op, condOPs) {
		return "", "", errors.New("nonsupport conditional op")
	}
	exp := p.pop()
	if exp == "" {
		return "", "", errors.New("empty value")
	}
	return op, exp, nil
}

// s is in ss or not.
func isIn(s string, ss []string) bool {
	for _, s0 := range ss {
		if s == s0 {
			return true
		}
	}
	return false
}

func (p *parser) peek() string {
	peeked, _ := p.peekWithLength()
	return peeked
}

func (p *parser) pop() string {
	peeked, n := p.peekWithLength()
	p.offset += n
	p.popWhitespace()
	return peeked
}

func (p *parser) popWhitespace() {
	for ; p.offset < len(p.conditions) && unicode.IsSpace(rune(p.conditions[p.offset])); p.offset++ {

	}
}

func (p *parser) peekWithLength() (string, int) {
	if p.offset >= len(p.conditions) {
		return "", 0
	}
	for _, rWord := range reservedWords {
		token := strings.ToUpper(p.conditions[p.offset:min(len(p.conditions),
			p.offset+len(rWord))])
		if token == rWord {
			return token, len(token)
		}
	}
	if p.conditions[p.offset] == '\'' { // Quoted string
		return p.peekQuotedStringWithLength()
	}
	return p.peekIdentifierWithLength()
}

func (p *parser) peekQuotedStringWithLength() (string, int) {

	for i := p.offset + 1; i < len(p.conditions); i++ {
		if p.conditions[i] == '\'' {
			return p.conditions[p.offset+1 : i], len(p.conditions[p.offset+1:i]) + 2 // +2 for the two quotes
		}
	}
	return "", 0
}

func (p *parser) peekIdentifierWithLength() (string, int) {
	for i := p.offset; i < len(p.conditions); i++ {
		if matched, _ := regexp.MatchString(`[a-zA-Z0-9_*]`, string(p.conditions[i])); !matched {
			return p.conditions[p.offset:i], len(p.conditions[p.offset:i])
		}
	}
	return p.conditions[p.offset:], len(p.conditions[p.offset:])
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
